{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "220c4b69bb26ce5d",
   "metadata": {},
   "source": [
    "https://huggingface.co/datasets/pleisto/wikipedia-cn-20230720-filtered"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "edbb5fbd-b34d-4792-868c-58c27330db57",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "# !pip install \"modelscope[framework]\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "530f9ff87b7d1f6e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# autodl平台上要更新一下torch\n",
    "# !pip3 install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "b99c559e4b93fe28",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-08T05:23:55.148415Z",
     "start_time": "2025-06-08T05:23:55.144Z"
    }
   },
   "outputs": [],
   "source": [
    "import json\n",
    "import os\n",
    "\n",
    "DATA_DIR = \"./gpt2-chinese/data\"\n",
    "MODEL_DIR = \"./gpt2-chinese/model\"\n",
    "TOKENIZER_PATH = \"./gpt2-chinese/tokenizer\"\n",
    "LOG_PATH = \"./gpt2-chinese/log\"\n",
    "os.makedirs(DATA_DIR, exist_ok=True)\n",
    "os.makedirs(MODEL_DIR, exist_ok=True)\n",
    "os.makedirs(TOKENIZER_PATH, exist_ok=True)\n",
    "os.makedirs(LOG_PATH, exist_ok=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "e3f029cca6f2594e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 启动tensorboard命令\n",
    "# tensorboard --logdir=gpt2-chinese/log"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "bc7273d63dae45ef",
   "metadata": {},
   "outputs": [],
   "source": [
    "# RAW_DATA = os.path.join(DATA_DIR, \"wikipedia-cn-20230720-filtered.json\")\n",
    "# TRAIN_DATA = os.path.join(DATA_DIR, \"train.txt\")\n",
    "\n",
    "# with open(RAW_DATA, \"r\") as f:\n",
    "#     data = json.load(f)\n",
    "\n",
    "# with open(TRAIN_DATA, \"w\", encoding=\"utf-8\") as f_out:\n",
    "#     for item in data:\n",
    "#         f_out.write(item[\"completion\"] + \"\\n\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "3994047b551cec42",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-08T05:24:02.961729Z",
     "start_time": "2025-06-08T05:23:59.778282Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Downloading Model from https://www.modelscope.cn to directory: /root/.cache/modelscope/hub/models/openai-community/gpt2\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2025-06-08 15:51:10,235 - modelscope - WARNING - Using branch: master as version is unstable, use with caution\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "1"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from modelscope import AutoTokenizer\n",
    "tokenizer = AutoTokenizer.from_pretrained('openai-community/gpt2')\n",
    "\n",
    "# tokenizer.pad_token = tokenizer.eos_token  # 设置填充token，不能和eos_token，不然后面训练的标签有问题，不能学到结束标记\n",
    "tokenizer.pad_token = '<PAD>'\n",
    "tokenizer.add_special_tokens({'pad_token': '<PAD>'})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "5484a581-85cd-4711-9fce-f757d1cf9120",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(50256, 50257)"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tokenizer.eos_token_id, tokenizer.pad_token_id"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "58da14bdafdbf4a7",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-08T05:24:05.406289Z",
     "start_time": "2025-06-08T05:24:04.881376Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "数据集大小: 21\n",
      "{'text': '《解开密码》是科幻小说作家倪匡的作品之一，卫斯理系列编号118。该作品于1999年8月16日完成，并于2000年7月1日出版。<|endoftext|>'}\n"
     ]
    }
   ],
   "source": [
    "from datasets import Dataset\n",
    "\n",
    "TRAIN_DATA = os.path.join(DATA_DIR, \"train1.txt\")\n",
    "\n",
    "def load_wiki_dataset(file_path):\n",
    "    data = []\n",
    "    with open(file_path, 'r', encoding='utf-8') as f:\n",
    "        for line in f:\n",
    "            data.append(line.replace('\\n', '')+tokenizer.eos_token)\n",
    "    return Dataset.from_dict({\"text\": data})\n",
    "\n",
    "# 加载数据集\n",
    "dataset = load_wiki_dataset(TRAIN_DATA)\n",
    "print(f\"数据集大小: {len(dataset)}\")\n",
    "print(dataset[0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "f3a32a9061164738",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-08T05:24:06.972732Z",
     "start_time": "2025-06-08T05:24:06.811037Z"
    }
   },
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "59a3a0417b0646448626812d47650c25",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Map (num_proc=4):   0%|          | 0/21 [00:00<?, ? examples/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "数据集大小: 21\n",
      "{'input_ids': [5099, 232, 164, 100, 96, 28156, 222, 43380, 228, 163, 254, 223, 5099, 233, 42468, 163, 100, 239, 33176, 119, 22887, 237, 46237, 112, 43291, 22522, 114, 161, 222, 103, 44293, 94, 21410, 43291, 161, 241, 223, 45298, 31660, 171, 120, 234, 39355, 104, 23877, 107, 49426, 228, 163, 111, 119, 26344, 245, 163, 120, 244, 20998, 115, 16817, 16764, 46237, 98, 43291, 161, 241, 223, 12859, 236, 18946, 33176, 112, 23, 17312, 230, 1433, 33768, 98, 22522, 234, 22755, 238, 171, 120, 234, 33176, 114, 12859, 236, 11024, 33176, 112, 22, 17312, 230, 16, 33768, 98, 49035, 118, 48304, 16764, 50256, 50257, 50257, 50257, 50257, 50257, 50257, 50257, 50257, 50257, 50257, 50257, 50257, 50257, 50257, 50257, 50257, 50257, 50257, 50257, 50257, 50257, 50257, 50257, 50257, 50257, 50257], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}\n"
     ]
    }
   ],
   "source": [
    "def tokenize_function(examples):\n",
    "    return tokenizer(\n",
    "        examples[\"text\"],\n",
    "        truncation=True,\n",
    "        padding='max_length',\n",
    "        max_length=128,  # GPT模型通常使用512长度\n",
    "        return_tensors=\"pt\"\n",
    "    )\n",
    "\n",
    "tokenized_dataset = dataset.map(\n",
    "    tokenize_function,\n",
    "    batched=True,\n",
    "    remove_columns=[\"text\"],\n",
    "    num_proc=4  # 使用多进程加速\n",
    ")\n",
    "print(f\"数据集大小: {len(tokenized_dataset)}\")\n",
    "print(tokenized_dataset[0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "67c594d9-b93b-482c-9a30-ef01cca1f9eb",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'卫斯理自知自己水平不足，于是将此交由勒曼医院研究。然而，勒曼医院久久未有回复，卫斯理亦渐渐淡忘此事。<|endoftext|><PAD><PAD><PAD><PAD><PAD><PAD><PAD><PAD><PAD><PAD><PAD><PAD><PAD><PAD><PAD><PAD>'"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tokenizer.decode(tokenized_dataset[3]['input_ids'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "697b063acb97516",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-08T05:24:08.610781Z",
     "start_time": "2025-06-08T05:24:08.591090Z"
    }
   },
   "outputs": [],
   "source": [
    "from transformers import DataCollatorForLanguageModeling\n",
    "\n",
    "data_collator = DataCollatorForLanguageModeling(\n",
    "    tokenizer=tokenizer,\n",
    "    mlm=False,  # GPT使用因果语言建模(CLM)，不是MLM\n",
    ")\n",
    "\n",
    "split_dataset = tokenized_dataset.train_test_split(test_size=0.05)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "60d27b11-c751-4908-9bf5-66042698d4fc",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Input IDs:\n",
      " tensor([[28839,   101, 46763,   227, 12859,   233, 21410, 28938,   236, 17312,\n",
      "           253,   171,   120,   234, 37605,   241, 25001,   117, 12859,   228,\n",
      "           164,   100,    96, 26344,   108,   164,   249,   117, 40792, 17312,\n",
      "           231, 28938,   226,   163,   100,   235, 37955, 31965,   102, 33768,\n",
      "           114,   171,   120,   234,   165,   247,    97, 17739,   112, 33176,\n",
      "           116,   165, 45865,   163,   255,   231, 37955, 31965,   102,   162,\n",
      "           110,    94, 17312,   231, 49035,   118,   163,   236,   108, 13783,\n",
      "           244,   171,   120,   234,   162,   249,   112, 17739,   112, 33176,\n",
      "           116, 37605,   241, 40792, 21410, 37955, 31965,   102,   162,   110,\n",
      "            94, 17312,   231, 49035,   118,   163,   236,   108,   171,   120,\n",
      "           234, 28938,    99, 26344,   247, 31660, 22522,   248, 27670,   248,\n",
      "         49035,   118,   163,   236,   108, 37955, 45250,   223,   163,   223,\n",
      "           122, 49694,   122, 16764, 50256, 50257, 50257, 50257]])\n",
      "Attention Mask:\n",
      " tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n",
      "         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n",
      "         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n",
      "         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n",
      "         1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,\n",
      "         1, 1, 1, 1, 1, 0, 0, 0]])\n",
      "Labels:\n",
      " tensor([[28839,   101, 46763,   227, 12859,   233, 21410, 28938,   236, 17312,\n",
      "           253,   171,   120,   234, 37605,   241, 25001,   117, 12859,   228,\n",
      "           164,   100,    96, 26344,   108,   164,   249,   117, 40792, 17312,\n",
      "           231, 28938,   226,   163,   100,   235, 37955, 31965,   102, 33768,\n",
      "           114,   171,   120,   234,   165,   247,    97, 17739,   112, 33176,\n",
      "           116,   165, 45865,   163,   255,   231, 37955, 31965,   102,   162,\n",
      "           110,    94, 17312,   231, 49035,   118,   163,   236,   108, 13783,\n",
      "           244,   171,   120,   234,   162,   249,   112, 17739,   112, 33176,\n",
      "           116, 37605,   241, 40792, 21410, 37955, 31965,   102,   162,   110,\n",
      "            94, 17312,   231, 49035,   118,   163,   236,   108,   171,   120,\n",
      "           234, 28938,    99, 26344,   247, 31660, 22522,   248, 27670,   248,\n",
      "         49035,   118,   163,   236,   108, 37955, 45250,   223,   163,   223,\n",
      "           122, 49694,   122, 16764, 50256,  -100,  -100,  -100]])\n"
     ]
    }
   ],
   "source": [
    "batch = [split_dataset[\"train\"][i] for i in range(1)]\n",
    "collated_batch = data_collator(batch)\n",
    "print(\"Input IDs:\\n\", collated_batch[\"input_ids\"])\n",
    "print(\"Attention Mask:\\n\", collated_batch[\"attention_mask\"])\n",
    "print(\"Labels:\\n\", collated_batch[\"labels\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "e8abf23276cb9e51",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-08T05:24:17.623878Z",
     "start_time": "2025-06-08T05:24:13.247972Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "The new embeddings will be initialized from a multivariate normal distribution that has old embeddings' mean and covariance. As described in this article: https://nlp.stanford.edu/~johnhew/vocab-expansion.html. To disable this, use `mean_resizing=False`\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "模型参数量: 124,440,576\n"
     ]
    }
   ],
   "source": [
    "from transformers import GPT2Config, GPT2LMHeadModel\n",
    "\n",
    "config = GPT2Config()\n",
    "model = GPT2LMHeadModel(config)\n",
    "\n",
    "# 因为自定义了PAD，所以需要同步模型与tokenizer的词汇表大小\n",
    "model.resize_token_embeddings(len(tokenizer)) \n",
    "\n",
    "print(f\"模型参数量: {model.num_parameters():,}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "cf4bebfee9ef801d",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "# !pip install 'accelerate>=0.26.0'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "fba4a3c3e9919e83",
   "metadata": {},
   "outputs": [],
   "source": [
    "# import accelerate\n",
    "\n",
    "# accelerate.__version__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "dd699290079ad18",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-08T05:24:20.898929Z",
     "start_time": "2025-06-08T05:24:20.861632Z"
    }
   },
   "outputs": [],
   "source": [
    "from transformers import TrainingArguments\n",
    "\n",
    "# 配置训练参数\n",
    "training_args = TrainingArguments(\n",
    "    output_dir=MODEL_DIR,\n",
    "    overwrite_output_dir=True,\n",
    "    num_train_epochs=100,\n",
    "    save_strategy=\"steps\",\n",
    "    eval_strategy=\"no\",\n",
    "    per_device_train_batch_size=4,\n",
    "    logging_steps=50,\n",
    "    # logging_dir=LOG_PATH,\n",
    "    save_total_limit=2,\n",
    "    learning_rate=5e-5\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "9aed5fa507429ad5",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-08T05:25:15.315228Z",
     "start_time": "2025-06-08T05:24:23.310170Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "开始训练...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "`loss_type=None` was set in the config but it is unrecognised.Using the default loss: `ForCausalLMLoss`.\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "\n",
       "    <div>\n",
       "      \n",
       "      <progress value='500' max='500' style='width:300px; height:20px; vertical-align: middle;'></progress>\n",
       "      [500/500 00:27, Epoch 100/100]\n",
       "    </div>\n",
       "    <table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       " <tr style=\"text-align: left;\">\n",
       "      <th>Step</th>\n",
       "      <th>Training Loss</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <td>50</td>\n",
       "      <td>6.185900</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>100</td>\n",
       "      <td>1.903900</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>150</td>\n",
       "      <td>0.370000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>200</td>\n",
       "      <td>0.069900</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>250</td>\n",
       "      <td>0.026300</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>300</td>\n",
       "      <td>0.015500</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>350</td>\n",
       "      <td>0.012500</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>400</td>\n",
       "      <td>0.011200</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>450</td>\n",
       "      <td>0.010500</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>500</td>\n",
       "      <td>0.009900</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table><p>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "TrainOutput(global_step=500, training_loss=0.8615450564026833, metrics={'train_runtime': 28.0304, 'train_samples_per_second': 67.784, 'train_steps_per_second': 17.838, 'total_flos': 124113715200000.0, 'train_loss': 0.8615450564026833, 'epoch': 100.0})"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from transformers import Trainer\n",
    "\n",
    "# 创建Trainer\n",
    "trainer = Trainer(\n",
    "    model=model,\n",
    "    args=training_args,\n",
    "    train_dataset=split_dataset[\"train\"],\n",
    "    eval_dataset=split_dataset[\"test\"],\n",
    "    data_collator=data_collator\n",
    ")\n",
    "\n",
    "print(\"开始训练...\")\n",
    "trainer.train()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "5853297e31f1f0b2",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-08T05:25:46.891816Z",
     "start_time": "2025-06-08T05:25:45.928195Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('./gpt2-chinese/tokenizer/tokenizer_config.json',\n",
       " './gpt2-chinese/tokenizer/special_tokens_map.json',\n",
       " './gpt2-chinese/tokenizer/vocab.json',\n",
       " './gpt2-chinese/tokenizer/merges.txt',\n",
       " './gpt2-chinese/tokenizer/added_tokens.json',\n",
       " './gpt2-chinese/tokenizer/tokenizer.json')"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 保存最终模型\n",
    "trainer.save_model(MODEL_DIR)\n",
    "tokenizer.save_pretrained(TOKENIZER_PATH)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "dfbda73a-10b5-4999-9cf0-67660de76e1e",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "《解开密码》是科幻小说作家倪匡的作品之一，卫斯理系列编号118。该作品于1999年8月16日完成，并于2000年7月1日出版。\n"
     ]
    }
   ],
   "source": [
    "from transformers import GPT2LMHeadModel, AutoTokenizer, StoppingCriteria\n",
    "\n",
    "model = GPT2LMHeadModel.from_pretrained(MODEL_DIR)\n",
    "\n",
    "input_text = \"《解开密码》\"\n",
    "inputs = tokenizer(input_text, return_tensors=\"pt\")\n",
    "\n",
    "class EosStoppingCriteria(StoppingCriteria):\n",
    "    def __call__(self, input_ids, scores, **kwargs):\n",
    "        return input_ids[0][-1] == tokenizer.eos_token_id\n",
    "\n",
    "# 生成输出\n",
    "outputs = model.generate(\n",
    "    inputs.input_ids,\n",
    "    attention_mask=inputs.attention_mask,\n",
    "    stopping_criteria=[EosStoppingCriteria()],\n",
    "    max_new_tokens=200,\n",
    "    pad_token_id=tokenizer.eos_token_id,\n",
    "    eos_token_id=tokenizer.eos_token_id\n",
    ")\n",
    "\n",
    "result = tokenizer.decode(outputs[0], skip_special_tokens=True)\n",
    "print(result)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
